/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

 /***************************************************************************
 * Name: packet_manager.cpp
 *
 * Purpose: packet manager implementation
 *
 * Developer:
 *   wen.gu , 2020-09-02
 *
 * TODO:
 *
 ***************************************************************************/

 /******************************************************************************
 **    INCLUDES
 ******************************************************************************/
#include "panda/pkt/packet_manager.h"

#include "unzip.h" 
#include "panda/core/json.h"
#include "panda/sys/sys_utils.h"
#include "panda/com/client_requester.h"
#include "panda/per/file_storage.h"
#include "panda/per/key_value_storage.h"
#include "panda/core/directory.h"

#define LOG_TAG "pkgm"
#include "panda/core/log.h"

namespace panda
{
namespace pkt
{
/******************************************************************************
 **    MACROS
 ******************************************************************************/
#define PKT_INFO_FILE_NAME "packet.json"
#define PKT_TYPE           "type"
#define PKT_VERSION        "version"
#define PKT_NAME           "name"
#define PKT_OS_VERSION     "os_version"
#define PKT_MANIFEST       "manifest"


/** manifest item */
#define MF_ICONS    "icons"
#define MF_PER      "persistency"
#define MF_PROPERTY "property"

/** for persistency item */
#define PER_FILE_STOR "file_storage"
#define PER_KV_STOR   "kv_storage"

#define PKT_TYPE_APP "Application"
#define PKT_TYPE_LIB "Library"
#define PKT_TYPE_SRV  "Service"
#define PKT_TYPE_RES  "Resource"

/** for stop target */
#define BOOT_MANAGER  "boot_manager"
#define BOOT_STOP_TARGET "stop_target"
/******************************************************************************
 **    VARIABLE DEFINITIONS
 ******************************************************************************/
using panda::core::Value;
using panda::core::PandaErrc;

using namespace panda::sys;

struct PerItemInfo
{
    std::string name;
    std::string version;
};

class PacketManager::impl
{
public:
    bool getPacketInfo(unzFile pktFile, Value& val); /** from packet zip file */
    PacketType str2PktType(const std::string& typeStr);
    bool parsePacketInfo(const Value& val, PacketInfo& pktInfo);  /** parse packet info from packet zip file */

    PandaErrc getPacketInfo(const std::string& name, PacketType pt, Value& val);  /** from PandaOs */

    bool parsePacketInfo(const Value& val, PacketType pt, PacketInfo& pktInfo);  /** parse packet info from PandaOS */

    PerItemInfo getFileStorageInfo(const Value& val);
    PerItemInfo getKvStorageInfo(const Value& val);

public:
    PandaErrc decompressFiles(const std::string& dirName, const std::string& dstDir);
    bool checkOSVersion(const std::string& depOsVersion);
    bool checkPacketExist(const std::string& pktName, PacketType pt);
    bool checkPacketVersion(const std::string& orgVer, const std::string& newVer);
    bool checkPerVersion(const std::string& orgVer, const std::string& newVer);
    
    
    PandaErrc doInstall(const std::string& pktFile);
};

/******************************************************************************
 **    FUNCTION DEFINITIONS
 ******************************************************************************/

bool PacketManager::impl::getPacketInfo(unzFile pktFile, Value& val)
{

    if (unzLocateFile(pktFile, PKT_INFO_FILE_NAME, 1))
    {
        LOGE("not found packet id file\n");
        return false;
    }

    unz_file_info fileInfo;
    char fileName[256];
    if (unzGetCurrentFileInfo(pktFile, &fileInfo, fileName, sizeof(fileName) - 1, nullptr, 0, nullptr, 0))
    {
        LOGE("get packet id file info failed\n");
        return false;
    }

    int err = unzOpenCurrentFile(pktFile);

    if (UNZ_OK != err)
    {
        LOGE("open id file in pakcet compressed file failed(%d)\n", err);
        return false;
    }

    char* pBuf = new char[fileInfo.uncompressed_size];

    err = unzReadCurrentFile(pktFile, pBuf, fileInfo.uncompressed_size);

    if (err <= 0)
    {
        LOGE("read packet id data failed(%d)\n", err);
        delete[] pBuf;
        return false;
    }

    if (!panda::core::JsonParse(pBuf, pBuf + err, val))
    {
        LOGE("parse packet id file(%s) failed\n", pBuf);
        delete[] pBuf;
        return false;
    }
    
    delete[] pBuf;
    return true;
}


PacketType PacketManager::impl::str2PktType(const std::string& typeStr)
{
    if (typeStr == PKT_TYPE_APP)
    {
        return PacketType::Application;
    }
    else if (typeStr == PKT_TYPE_SRV)
    {
        return PacketType::Service;
    }
    else if (typeStr == PKT_TYPE_RES)
    {
        return PacketType::Resource;
    }
    else if (typeStr == PKT_TYPE_LIB)
    {
        return PacketType::Library;
    }

    LOGE("unknown packet type: %s\n", typeStr.c_str());
    return PacketType::Any;
}

bool PacketManager::impl::parsePacketInfo(const Value& val, PacketInfo& pktInfo)
{
    if (!val.isMember(PKT_TYPE))
    {
        LOGE("packet type section not found\n");
        return false;
    }

    pktInfo.type = str2PktType(val[PKT_TYPE].asString());

    if (pktInfo.type == PacketType::Any)
    {
        return false;
    }

    if (!val.isMember(PKT_NAME))
    {
        LOGE("packet name section not found\n");
        return false;
    }

    pktInfo.name = val[PKT_NAME].asString();

    if (!val.isMember(PKT_VERSION))
    {
        LOGE("packet version section not found\n");
        return  false;
    }

    pktInfo.version = val[PKT_VERSION].asString();

    if (!val.isMember(PKT_OS_VERSION))
    {
        LOGE("packet os version section not found\n");
        return false;
    }

    pktInfo.osVersion = val[PKT_OS_VERSION].asString();

    return true;
}

PandaErrc PacketManager::impl::getPacketInfo(const std::string& name, PacketType pt, Value& val)  /** from PandaOs */
{
    return PandaErrc::NotImplemented;
}

bool PacketManager::impl::parsePacketInfo(const Value& val, PacketType pt, PacketInfo& pktInfo)  /** parse packet info from PandaOS */
{
    return false;
}

PerItemInfo PacketManager::impl::getFileStorageInfo(const Value& val)
{
    PerItemInfo pii;
    if (val.isMember(MF_PER))
    {
        Value pers = val[MF_PER];
        if (pers.isMember(PER_FILE_STOR))
        {
            Value per_file = pers[PER_FILE_STOR];
            pii.name = per_file["name"].asString();
            pii.version = per_file["version"].asString();
        }
    }

    return pii;
}
PerItemInfo PacketManager::impl::getKvStorageInfo(const Value& val)
{
    PerItemInfo pii;
    if (val.isMember(MF_PER))
    {
        Value pers = val[MF_PER];
        if (pers.isMember(PER_KV_STOR))
        {
            Value per_kv = pers[PER_KV_STOR];
            pii.name = per_kv["name"].asString();
            pii.version = per_kv["version"].asString();
        }
    }
    return pii;
}


PandaErrc PacketManager::impl::decompressFiles(const std::string& dirName, const std::string& dstDir)
{
    return PandaErrc::NotImplemented;
}

bool PacketManager::impl::checkOSVersion(const std::string& depOsVersion)
{
    return false;
}

bool PacketManager::impl::checkPacketExist(const std::string& pktName, PacketType pt)
{
    return false;
}
bool PacketManager::impl::checkPacketVersion(const std::string& orgVer, const std::string& newVer)
{
    return false;
}

bool PacketManager::impl::checkPerVersion(const std::string& orgVer, const std::string& newVer)
{
    return false;
}



PandaErrc PacketManager::impl::doInstall(const std::string& pktFile)
{
    unzFile zf = unzOpen(pktFile.c_str());

    if (zf == nullptr)
    {
        LOGE("open packet file(%s) failed\n", pktFile.c_str());
        return PandaErrc::UnsupportedType;
    }
    Value pj;
    Value orgPj;  /** packet storage info from PandaOS */
    if (!getPacketInfo(zf, pj))
    {
        LOGE("get packet info failed\n");
        unzClose(zf);
        return PandaErrc::UnsupportedType;
    }

    PacketInfo pi;
    if (!parsePacketInfo(pj, pi))
    {
        LOGE("parse packet info failed\n");
        unzClose(zf);
        return PandaErrc::UnsupportedType;
    }


    if (checkPacketExist(pi.name, pi.type))
    {
        PacketInfo orgPi;
        PandaErrc res = getPacketInfo(pi.name, pi.type, orgPj);

        if (res != PandaErrc::OK)
        {
            LOGE("get PacketInfo from PandaOS failed\n");
            unzClose(zf);
            return PandaErrc::Undefined;
        }

        parsePacketInfo(orgPj, pi.type, orgPi);

        if (!checkPacketVersion(orgPi.version, pi.version))
        {
            LOGE("exist packet's version(%s) newer than current packet(%s)\n",
                orgPi.version.c_str(), pi.version.c_str());
            unzClose(zf);
            return PandaErrc::AlreadyExisted;
        }

        Value param;
        Value reply;
        param["name"] = pi.name;
        panda::com::ClientRequester::methodCall(BOOT_MANAGER, BOOT_STOP_TARGET, param, reply);
    }

    std::string osType = panda::sys::GetHostType();
    std::string platformType = panda::sys::GetPlatformType();
    std::string middlePath = osType + "/" + platformType;


    decompressFiles("bin/" + middlePath, panda::sys::GetUserBinDir());    
    decompressFiles("lib/" + middlePath, panda::sys::GetUserLibDir());
    decompressFiles("config", panda::sys::GetUserConfigDir() + pi.name);
    decompressFiles("res/data", panda::sys::GetExtraDir() + pi.name + "/data");

    if (pj.isMember(PKT_MANIFEST))
    {
        Value mf = pj[PKT_MANIFEST];

        if (mf.isMember(MF_ICONS))
        {
            Value icons = mf[MF_ICONS];
            std::string iconDir = panda::sys::GetIconDir() + pi.name;

            for (uint32_t i = 0; i < icons.size(); i++)
            {
                decompressFiles(icons[i].asString(), iconDir);
            }
        }

        if (mf.isMember(MF_PER))
        {
            Value pers = mf[MF_PER];
            if (pers.isMember(PER_FILE_STOR)) /** have file storage persistency */
            {
                Value per_file = pers[PER_FILE_STOR];
                std::string perf_version = per_file["version"].asString();
                std::string perf_name = per_file["name"].asString();

                PerItemInfo pii = getFileStorageInfo(orgPj);

                if (!checkPerVersion(pii.version, perf_version) || (pii.name != perf_name))
                {/** if missmatch, then reinstall this persistency data */
                    std::string fstorPath = GetPersistenceDir() + pii.name;
                    panda::core::Directory::RmDir(fstorPath); /** delete old */
                    
                    fstorPath = GetPersistenceDir() + perf_name;
                    panda::core::Directory::MkDir(fstorPath); /** create new */
                    Value files = per_file["files"];
                    for (uint32_t i = 0; i < files.size(); i++)
                    {
                        decompressFiles(files[i].asString(), fstorPath);
                    }
                }   
                /** if match, then do nothing */
            }

            if (pers.isMember(PER_KV_STOR))    /** have key-value persistency */
            {
                Value per_kv = pers[PER_KV_STOR];
                std::string perkv_version = per_kv["version"].asString();
                std::string perkv_name = per_kv["name"].asString();

                PerItemInfo pii = getKvStorageInfo(orgPj);
                if (!checkPerVersion(pii.version, perkv_version) || (pii.name != perkv_name))
                {/** if missmatch, then reinstall this persistency data */
                    
                    std::string kvsPath = GetKvStorageDir() + pii.name;

                    ::remove(kvsPath.c_str()); /** delete old */

                    kvsPath = GetKvStorageDir() + perkv_name;
                    FILE* fp = fopen(kvsPath.c_str(), "wb+"); /** create new */
                    
                    if (fp)
                    {
                        fflush(fp); /** todo, refine me */
                        fclose(fp);
                        std::shared_ptr<panda::per::KeyValueStorage> kvs = panda::per::KeyValueStorage::OpenKeyValueStorage(perkv_name);
                        if (kvs)
                        {
                            Value kvs_val = per_kv["kvs"];
                            std::vector<std::string> keys = kvs_val.getMembers();
                            for (size_t i = 0; i < keys.size(); i++)
                            {
                                std::string& ikey = keys[i];
                                Value val = kvs_val[ikey];
                                using panda::core::ValueType;
                                switch (val.type())  /** todo, refine manifest structure, add data type */
                                {
                                case ValueType::String: kvs->setValue(ikey, val.asString()); break;
                                case ValueType::Boolean: kvs->setValue(ikey, val.asBool()); break;
                                case ValueType::Int:     kvs->setValue(ikey, val.asInt()); break;
                                case ValueType::Real:    kvs->setValue(ikey, val.asFloat()); break;
                                default:
                                    LOGE("unsupported kvs type(%d) for now\n", (int)val.type());
                                    break;
                                }                               
                            }

                            kvs->syncToStorage();
                        }
                    }
                    else
                    {
                        LOGE("create kvs file(%s) failed(%s)\n", kvsPath.c_str(), strerror(errno));
                    }
                }
            }
        }
    }

    return PandaErrc::OK; /** todo, refine me?? */
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////



} /** namespace pkt */

} /** namespace panda */
